﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Media;
using BSU.GraduateWork.LinesIn3D;
using BSU.Math.Geometry;

namespace BSU.GraduateWork.Visualizer
{
    public class VectorVisualizer
    {
        private readonly LineGroup _lines;


        public IVectorVisualizerUI Page { get; private set; }
        public LineGroup Lines { get { return _lines; } }

        private readonly List<VectorUI> _vectors = new List<VectorUI>();


        public VectorVisualizer(IVectorVisualizerUI page)
        {
            Page = page;
            _lines = new LineGroup(AxisCenter);
            Lines.LineAdded += Lines_LineAdded;
            Lines.LineRemoved += Lines_LineRemoved;
        }

        public void Initialize()
        {
            DrawAxes();

            DrawSubAxes();

            SetInitialPosition();

            //SetVectorsPassedToQueryString();
            SetPointsPassedToParams();
        }

        public Brush AxisBrush { get { return Page.AxisBrush; } }
        public Brush SubAxisBrush { get { return Page.SubAxisBrush; } }
        public double AxisLength { get { return Page.AxisLength; } }
        public Point AxisCenter { get { return Page.AxisCenter; } }
        public double AxisArrowSize { get { return Page.AxisArrowSize; } }
        public double LetterShift { get { return Page.LetterShift; } }
        public double LetterWidth { get { return Page.LetterWidth; } }
        public double LetterHeight { get { return Page.LetterHeight; } }

        public IList<VectorUI> Vectors { get { return _vectors; } }
        public IEnumerable<Line2D> VectorLines
        {
            get
            {
                foreach (var vectorUI in Vectors)
                {
                    yield return vectorUI;
                }
            }
        }

        public void SetInitialPosition()
        {

            //Lines.RotateToDegrees(20, 40, -13);
            Lines.RotateToDegrees(90, 180, 45);
            Lines.ShiftDegreesAroundX(-20);
            Lines.ShiftDegreesAroundY(-20);

            Lines.ReDraw();

            UpdateCurrrentPage();
        }

        private void SetVectorsPassedToQueryString()
        {
            var pageUrl = HtmlPage.Document.DocumentUri.AbsoluteUri;
            var qMarkIndex = pageUrl.IndexOf('?');
            var vVarIndex = pageUrl.IndexOf("vectors=");
            if (qMarkIndex <= 0 || vVarIndex <= 0)
            {
                return;
            }
            var vVarEndIndex = pageUrl.IndexOf("&", vVarIndex + 1);
            if (vVarEndIndex < 0) vVarEndIndex = pageUrl.Length;
            var vectors = pageUrl.Substring(vVarIndex, vVarEndIndex - vVarIndex);
            var vectorsArray = vectors.Replace("vectors=", "").Split('v');
            for (var i = 0; i < vectorsArray.Length; i++)
            {
                var partsArray = vectorsArray[i].Split('/');
                if (partsArray.Length < 6)
                {
                    continue;
                }
                double x1;
                double y1;
                double z1;
                double x2;
                double y2;
                double z2;
                if (
                    !Double.TryParse(partsArray[0], out x1) ||
                    !Double.TryParse(partsArray[1], out y1) ||
                    !Double.TryParse(partsArray[2], out z1) ||
                    !Double.TryParse(partsArray[3], out x2) ||
                    !Double.TryParse(partsArray[4], out y2) ||
                    !Double.TryParse(partsArray[5], out z2)
                    )
                {
                    continue;
                }
                x1 = EnsureRange(x1, Page.AxisArrowSize);
                y1 = EnsureRange(y1, Page.AxisArrowSize);
                z1 = EnsureRange(z1, Page.AxisArrowSize);
                x2 = EnsureRange(x2, Page.AxisArrowSize);
                y2 = EnsureRange(y2, Page.AxisArrowSize);
                z2 = EnsureRange(z2, Page.AxisArrowSize);
                var vector = new VectorUI(Page.VectorFactor)
                                 {
                                     BeginningX = x1,
                                     BeginningY = y1,
                                     BeginningZ = z1,
                                     EndX = x2,
                                     EndY = y2,
                                     EndZ = z2,
                                     Brush = Page.NextColor
                                 };
                try
                {
                    AddVector(vector);
                }
                catch (Exception)
                {
                    continue;
                }
            }
        }

        private void SetPointsPassedToParams()
        {
            const char pointsDelimeter = '|';
            const char pointComponentsDelimeter = '/';

            var value = (string)App.Current.Resources[App.DataSourceParamName];

            if (String.IsNullOrEmpty(value))
            {
                return;
            }

            var points = value.Replace('.', ',').Split(pointsDelimeter);

            var points3D = new List<Point3D>();

            for (var i = 0; i < points.Length; i++)
            {
                var partsArray = points[i].Split(pointComponentsDelimeter);
                if (partsArray.Length < 3)
                {
                    continue;
                }
                double x;
                double y;
                double z;
                if (
                    !Double.TryParse(partsArray[0], out x) ||
                    !Double.TryParse(partsArray[1], out y) ||
                    !Double.TryParse(partsArray[2], out z)
                    )
                {
                    continue;
                }
                x = 10 * EnsureRange(x, Page.AxisArrowSize);
                y = 10 * EnsureRange(y, Page.AxisArrowSize);
                z = 100 * EnsureRange(z, Page.AxisArrowSize);

                var point3D = new Point3D(x, y, z);

                points3D.Add(point3D);

                var vector = new VectorUI(Page.VectorFactor)
                {
                    BeginningX = x,
                    BeginningY = y,
                    BeginningZ = z,
                    EndX = x,
                    EndY = y,
                    EndZ = z,
                    Brush = Page.NextColor
                };
                try
                {
                    AddVector(vector);
                }
                catch (Exception)
                {
                    continue;
                }
            }

            var count = (int)System.Math.Sqrt(points3D.Count);

            //var p = new Point3D[count][];

            //for (int i = 0; i < count; i++)
            //{
            //    p[i] = new Point3D[count];
            //    for (int j = 0; j < count; j++)
            //    {
            //        p[i][j] = points3D[count * i + j];
            //    }
            //}
            //for (int i = 0; i < count - 1; i++)
            //{
            //    for (int j = 0; j < count - 1; j++)
            //    {
            //        var vector1 = new VectorUI(Page.VectorFactor)
            //        {
            //            BeginningX = p[i][j + 1].X,
            //            BeginningY = p[i][j + 1].Y,
            //            BeginningZ = p[i][j + 1].Z,
            //            EndX = p[i + 1][j + 1].X,
            //            EndY = p[i + 1][j + 1].Y,
            //            EndZ = p[i + 1][j + 1].Z,
            //            Brush = Page.NextColor
            //        };
            //        var vector2 = new VectorUI(Page.VectorFactor)
            //        {
            //            BeginningX = p[i + 1][j].X,
            //            BeginningY = p[i + 1][j].Y,
            //            BeginningZ = p[i + 1][j].Z,
            //            EndX = p[i + 1][j + 1].X,
            //            EndY = p[i + 1][j + 1].Y,
            //            EndZ = p[i + 1][j + 1].Z,
            //            Brush = Page.NextColor
            //        };
            //        try
            //        {
            //            AddVector(vector1);
            //            AddVector(vector2);
            //        }
            //        catch (Exception)
            //        {
            //            continue;
            //        }
            //    }
            //}
            //for (int i = 0; i < count - 1; i++)
            //{
            //    var vector1 = new VectorUI(Page.VectorFactor)
            //    {
            //        BeginningX = p[i][0].X,
            //        BeginningY = p[i][0].Y,
            //        BeginningZ = p[i][0].Z,
            //        EndX = p[i + 1][0].X,
            //        EndY = p[i + 1][0].Y,
            //        EndZ = p[i + 1][0].Z,
            //        Brush = Page.NextColor
            //    };
            //    var vector2 = new VectorUI(Page.VectorFactor)
            //    {
            //        BeginningX = p[0][i].X,
            //        BeginningY = p[0][i].Y,
            //        BeginningZ = p[0][i].Z,
            //        EndX = p[0][i + 1].X,
            //        EndY = p[0][i + 1].Y,
            //        EndZ = p[0][i + 1].Z,
            //        Brush = Page.NextColor
            //    };
            //    try
            //    {
            //        AddVector(vector1);
            //        AddVector(vector2);
            //    }
            //    catch (Exception)
            //    {
            //        continue;
            //    }
            //}

            //var vector = new VectorUI(Page.VectorFactor)
            //{
            //    BeginningX = x,
            //    BeginningY = y,
            //    BeginningZ = z,
            //    EndX = x,
            //    EndY = y,
            //    EndZ = z,
            //    Brush = Page.NextColor
            //};
            //try
            //{
            //    AddVector(vector);
            //}
            //catch (Exception)
            //{
            //    continue;
            //}
        }

        private static double EnsureRange(double value, double limit)
        {
            if (value > limit) return limit;
            if (value < (-1 * limit)) return -1 * limit;
            return value;
        }

        public void AddVector(VectorUI vector)
        {
            if (Vectors.Find((vUI, i) => vUI.Equals(vector)) != null)
            {
                throw new Exception(SR.VectorIsAlreadyDefined.Args(vector));
            }
            vector.ID = "Vector" + Vectors.Count;

            Vectors.Add(vector);

            Lines.AddLine(vector);

            UpdateCurrrentPage();

            Lines.ReDraw();
        }

        public void UpdateCurrrentPage()
        {
            Page.CurrentPageUrl = Vectors.ToLink(HtmlPage.Document.DocumentUri.AbsoluteUri);
        }

        private void Lines_LineAdded(object sender, LineChangedEventArgs e)
        {
            Page.AddLine(e.Line);
        }

        private void Lines_LineRemoved(object sender, LineChangedEventArgs e)
        {
            Page.RemoveLine(e.Line);
        }

        private void DrawAxes()
        {
            Lines.DefaultBrush = AxisBrush;

            // draw Y axis
            Lines.AddLine("Y0",
                               new[] { 0D, 0, 0 },
                               new[] { AxisLength, 0, 0 });
            // draw Y axis arrow
            Lines.AddLine("Y0_Arrow1",
                               new[] { AxisLength, 0, 0 },
                               new[] { AxisLength - AxisArrowSize, AxisArrowSize * -1, 0 });
            Lines.AddLine("Y0_Arrow2",
                               new[] { AxisLength, 0, 0 },
                               new[] { AxisLength - AxisArrowSize, AxisArrowSize, 0 });
            // draw Y letter
            Lines.AddLine("Y0_Letter1",
                               new[] { AxisLength + LetterShift, AxisArrowSize * -1, 0 },
                               new[] { AxisLength + LetterShift + LetterWidth, LetterHeight / 2D, 0 });
            Lines.AddLine("Y0_Letter2",
                               new[] { AxisLength + LetterShift, AxisArrowSize, 0 },
                               new[] { AxisLength + LetterShift + LetterWidth, (LetterHeight / 2D) * -1, 0 });

            // draw Y axis
            Lines.AddLine("Z0",
                               new[] { 0D, 0, 0 },
                               new[] { 0, AxisLength * -1, 0 });
            // draw Y axis arrow
            Lines.AddLine("Z0_Arrow1",
                               new[] { 0, AxisLength * -1, 0 },
                               new[] { AxisArrowSize * -1, AxisLength * -1 + AxisArrowSize, 0 });
            Lines.AddLine("Z0_Arrow2",
                               new[] { 0, AxisLength * -1, 0 },
                               new[] { AxisArrowSize, AxisLength * -1 + AxisArrowSize, 0 });
            // draw Y letter
            //Lines.AddLine("Y0_Letter1",
            //                   new[] { 0 + LetterWidth / 2, AxisLength * -1 - (LetterShift + LetterHeight), 0 },
            //                   new[] { 0, AxisLength * -1 - (LetterShift + LetterHeight / 2), 0 });
            //Lines.AddLine("Y0_Letter2",
            //                   new[] { 0 - LetterWidth / 2, AxisLength * -1 - (LetterShift + LetterHeight), 0 },
            //                   new[] { 0, AxisLength * -1 - (LetterShift + LetterHeight/2), 0 });
            //Lines.AddLine("Y0_Letter3",
            //                   new[] { 0, AxisLength * -1 - (LetterShift + LetterHeight / 2), 0 },
            //                   new[] { 0, AxisLength * -1 - LetterShift, 0 });

            // draw X axis
            Lines.AddLine("X0",
                               new[] { 0D, 0, 0 },
                               new[] { 0, 0, AxisLength });
            // draw X axis arrow
            Lines.AddLine("X0_Arrow1",
                               new[] { 0, 0, AxisLength },
                               new[] { 0, AxisArrowSize * -1, AxisLength - AxisArrowSize });
            Lines.AddLine("X0_Arrow2",
                               new[] { 0, 0, AxisLength },
                               new[] { 0, AxisArrowSize, AxisLength - AxisArrowSize });
            // draw X letter
            Lines.AddLine("X0_Letter1",
                               new[] { 0, 0 - LetterHeight / 2, AxisLength + (LetterWidth + LetterShift) },
                               new[] { 0, 0 + LetterHeight / 2, AxisLength + (LetterWidth - LetterShift) });
            Lines.AddLine("X0_Letter2",
                               new[] { 0, 0 - LetterHeight / 2, AxisLength + LetterShift },
                               new[] { 0, 0 + LetterHeight / 2, AxisLength + (LetterShift + LetterWidth) });
            //Lines.AddLine("Z0_Letter1",
            //                   new[] { 0, 0 - LetterHeight / 2, AxisLength + (LetterShift + LetterWidth) },
            //                   new[] { 0, 0 - LetterHeight / 2, AxisLength + LetterShift });
            //Lines.AddLine("Z0_Letter3",
            //                   new[] { 0, 0 + LetterHeight / 2, AxisLength + (LetterShift + LetterWidth) },
            //                   new[] { 0, 0 + LetterHeight / 2, AxisLength + LetterShift });

            // draw X letter
            //Lines.AddLine("X0_Letter1",
            //                   new[] { AxisLength + LetterShift, AxisArrowSize * -1, 0 },
            //                   new[] { AxisLength + LetterShift + LetterWidth, LetterHeight / 2D, 0 });
            //Lines.AddLine("X0_Letter2",
            //                   new[] { AxisLength + LetterShift, AxisArrowSize, 0 },
            //                   new[] { AxisLength + LetterShift + LetterWidth, (LetterHeight / 2D) * -1, 0 });

        }

        private void DrawSubAxes()
        {
            Lines.DefaultBrush = SubAxisBrush;

            Lines.AddLine("X-0",
                               new[] { AxisLength * -1, 0, 0 },
                               new[] { 0D, 0, 0 });

            Lines.AddLine("Y-0",
                               new[] { 0D, AxisLength, 0 },
                               new[] { 0D, 0, 0 });

            Lines.AddLine("Z-0",
                               new[] { 0D, 0, AxisLength * -1 },
                               new[] { 0D, 0, 0 });

            var step = 20;
            var axisLength = (AxisLength - AxisArrowSize - LetterShift);

            for (var i = 0; (i + step) < axisLength; i += step)
            {
                // sub axes starting from X axes
                Lines.AddLine("XY" + ((i / step) + 1),
                                   new[] { i + step, 0, 0D },
                                   new[] { i + step, axisLength * -1, 0D });

                Lines.AddLine("XZ" + ((i / step) + 1),
                                   new[] { i + step, 0, 0D },
                                   new[] { i + step, 0, axisLength });

                // sub axes starting from Y axes
                Lines.AddLine("YX" + ((i / step) + 1),
                                   new[] { 0, i * -1 - step, 0D },
                                   new[] { axisLength, i * -1 - step, 0D });

                Lines.AddLine("YZ" + ((i / step) + 1),
                                   new[] { 0D, i * -1 - step, 0D },
                                   new[] { 0D, i * -1 - step, axisLength });

                // sub axes starting from Z axes
                Lines.AddLine("ZX" + ((i / step) + 1),
                                   new[] { 0D, 0, i + step },
                                   new[] { 0D, axisLength * -1, i + step });

                Lines.AddLine("ZY" + ((i / step) + 1),
                                   new[] { 0D, 0D, i + step },
                                   new[] { axisLength, 0D, i + step });
            }
        }




        public void MouseMove(double xChange, double yChange)
        {
            // here X is Y and Y is X because moving along X rotates around Y and moving along Y rotates around X
            // Y rotation is multiplied by -1 because 0,0 point on the screen is at the top and not at the bottom
            Lines.ShiftDegrees(yChange, xChange * -1, 0);
            Lines.ReDraw();
        }


        public void ClearVectors()
        {
            foreach (var line in VectorLines)
            {
                Lines.RemoveLine(line);
            }
            Vectors.Clear();
            Page.ResetColorIndex();
            UpdateCurrrentPage();
        }
    }
}